Technotes
The Notification Manager: Problems & Fixes
This Technote describes two serious problems in the Notification Manager (NM), one having to do with activate events and the other with update events. These problems can cause windows in your application to be drawn redundantly or not at all. This Technote provides a workaround for the active event problem and some sample code, with explanations, for fixing the update event problem. If you're an application or app framework developer and want to ensure the windows in your application(s) are always updated and activated properly, you should read this Note.
This Technote augments the information presented in three chapters of Inside Macintosh
:"Event Manager" (Chapter 2) and "Window Manager" (Chapter 4) of Macintosh Toolbox Essentials
and "Notification Manager" (Chapter 5) of Processes
Defining Notification Manager ProblemsYou can use the Notification Manager to present the user with a modal dialog (alert) that opens in front of the windows of all applications. This dialog actually appears in the window list of the frontmost application during its call to WaitNextEvent.
The fact that Dialog Manager, and in particular ModalDialog and the standard dialog filter, provide imperfect event handling means that some window-oriented events are "swallowed" (i.e., never provided to your app) while the dialog is present.
Swallowed Deactivate Events (and Redundant Activate Events)Your application does not receive a deactivate event for the (soon-to-be-former) front window when the Notification Manager's dialog appears. This is because ModalDialog has its own event loop (partially implemented by the standard dialog filter) and has no way of passing the deactivate event off to your app's event loop.When the user dismisses NM's dialog, your app receives a redundant activate event for the (recently-reinstated) front window. Your app can make sure it doesn't logically activate a window (i.e., enable text fields, etc.) redundantly by simply checking to see if the window is already active before logically activating it.
Swallowed Update EventsIn order to understand how update events get swallowed, you need to understand how they would have been generated in the first place.Each window has a region, expressed in global coordinates, which describes the portion of the window that needs redrawing. This is called the window's update region. During WaitNextEvent, the Event Manager, Window Manager, and Process Manager collaborate in walking the current window list. If the update region of a window is found to be non-empty, they generate an update event for that window. When your application receives the update event, it calls BeginUpdate, (re)draws the image for the window, and calls EndUpdate. The BeginUpdate/EndUpdate pair of calls empties the update region for the window so that subsequent searches for windows which need updating do not find that window. These update regions are the key to understanding how the Notification Manager swallows update events. The standard dialog filter, to which NM's dialog filter passes control, wants to ensure that background apps get processing time by "solving" the problem described in Macintosh Technical Note TB37, "Pending Update Perils." The standard filter simply calls BeginUpdate and EndUpdate every time it's given an update event, regardless of the window for which the event is bound. This results in all windows in the current window list having their update regions emptied almost immediately. As a consequence, no update events are generated for those windows, even though they need to be (re)drawn. When the user dismisses NM's dialog, only the windows covered by NM's dialog get update events, and then only for the region covered by NM's dialog. There is nothing straightforward your app can do to prevent swallowed update events. While your app innocently waits for a call to WaitNextEvent to return, NM suddenly puts up its dialog and seizes control of the event loop until the user dismisses the dialog. Your app can't even predict when this will happen, much less prevent it or easily work around it. Using the Sample Code Library "UpdateRegionSaver" to Work Around the ProblemAlthough Apple may fix both of these problems in a future system software release, you might consider using some of the sample code presented here as a workaround. The sample code will continue to work even if there is a fix to the system software.Most users of UpdateRegionSaver will only need to make three very simple calls, shown in this sample: SaveUpdateRegions, RestoreUpdateRegions, and DeleteUpdateRegions.
#ifndef __EVENTS__ # includeFor a full listing of each of the three calls, refer to Appendix A at the end of this Technote. UpdateRegionSaver ReferenceThis section describes the data structures and routines specific to the UpdateRegionSaver library.
Data StructuresThe UpdateRegionSaver library manipulates a data structure of type UpdateRegionSaver.
typedef struct UpdateRegionSaver { RgnHandle fRgnH; WindowRef fRef; struct UpdateRegionSaver *fNext; } UpdateRegionSaver; Field Descriptions
UpdateRegionSaver RoutinesTo get a list of update regions associated with the windows in the current window list, call SaveUpdateRegions. To restore the regions to their owning windows, call RestoreUpdateRegions. To destroy the list of regions, call DeleteSavedUpdateRegions.
SaveUpdateRegionsTo save the update regions associated with the windows in your application's window list, call SaveUpdateRegions.SaveUpdateRegions returns a pointer to the root of simple singly-linked list which contains a node for each window. In each node your app will find a window pointer, a copy of the window's update region, and a pointer to the next node.
RestoreUpdateRegionsTo restore a list of saved update regions to the windows from which they came, call RestoreUpdateRegions.RestoreUpdateRegions copies the regions in the list and converts the copies to the local coordinates of each window before calling InvalRgn to merge the region into any update region which may already be there.
DeleteSavedUpdateRegionsTo delete a list of saved update regions, call DeleteSavedUpdateRegions.SummaryThe Notification Manager (NM) may present a Dialog Manager dialog whenever your application calls WaitNextEvent. This impedes the flow of window-related events (such as update or activate) to your application's event loop. Make sure your app doesn't logically activate a window which is already active. Use the UpdateRegionSaver library (or something like it) to ensure your app will always get the update events it requires.
Further Reference
Appendix AUpdateRegionSaver.h#pragma once #ifndef __WINDOWS__ # include <Windows.h>#endif typedef struct UpdateRegionSaver { // We don't care about alignment since this is an internal // runtime-only structure. RgnHandle fRgnH; WindowRef fRef; struct UpdateRegionSaver *fNext; } UpdateRegionSaver; // I can't figure why this next #ifdef would be necessary for // pascal funcs,but CW7 for PPC says it is. #ifdef __cplusplus extern "C" { #endif pascal void RestoreUpdateRegions (UpdateRegionSaver *); pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *); pascal UpdateRegionSaver * SaveUpdateRegions (void); #ifdef __cplusplus } #endif UpdateRegionSaver.c#ifndef __LOWMEM__ # include <LowMem.h>#endif #include "UpdateRegionSaver.h" static pascal Boolean IsWindowStillAround (WindowRef ref) { WindowRef scan = LMGetWindowList ( ); while (scan) { if (scan == ref) break; scan = GetNextWindow (scan); } return !!scan; } pascal void RestoreUpdateRegions (UpdateRegionSaver *ursp) { while (ursp) { if (!EmptyRgn (ursp->fRgnH) && IsWindowStillAround (ursp->fRef)) { RgnHandle localUpdateRgn = NewRgn ( ); if (localUpdateRgn) { Point zero; GrafPtr keep = qd.thePort; SetPort (ursp->fRef); zero.h = qd.thePort->portRect.left; zero.v = qd.thePort->portRect.top; GlobalToLocal (&zero); CopyRgn (ursp->fRgnH,localUpdateRgn); OffsetRgn (localUpdateRgn,zero.h,zero.v); InvalRgn (localUpdateRgn); SetPort (keep); DisposeRgn (localUpdateRgn); } } ursp = ursp->fNext; } } pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *ursp) { while (ursp) { UpdateRegionSaver *next = ursp->fNext; DisposeRgn (ursp->fRgnH); DisposePtr ((Ptr) ursp); ursp = next; } } pascal UpdateRegionSaver * SaveUpdateRegions (void) { // // This function saves as many update regions as it can. // If for some reason memory is so low that some regions // cannot be saved, this function makes a best effort. // (Its best effort is rather stupid, but it does try.) // UpdateRegionSaver *root = nil; WindowRef scan = LMGetWindowList ( ); while (scan) { UpdateRegionSaver *newUpdateRegionSaver = (UpdateRegionSaver *) NewPtr (sizeof (UpdateRegionSaver)); if (!MemError ( )) { RgnHandle rgnH = NewRgn ( ); if (!rgnH) { DisposePtr ((Ptr) newUpdateRegionSaver); newUpdateRegionSaver = nil; } else { GetWindowUpdateRgn (scan,rgnH); newUpdateRegionSaver->fRgnH = rgnH; newUpdateRegionSaver->fRef = scan; newUpdateRegionSaver->fNext = root; root = newUpdateRegionSaver; } } scan = GetNextWindow (scan); } return root; } Technotes Previous Technote | Contents | Next Technote |